package p.ithorns.framework.schedule;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.TriggerTask;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ScheduledTaskPostProcessor
 *
 * @author ithorns
 * @version 1.0
 * @date 2021/9/17 15:04
 */
public class ScheduledTaskPostProcessor implements
        BeanPostProcessor,
        BeanFactoryAware,
        EmbeddedValueResolverAware,
        SmartInitializingSingleton,
        Ordered {
    private final static String CRON_DISABLED = "-";

    private final Log logger = LogFactory.getLog(getClass());

    private final Set<Class<?>> nonAnnotatedClasses =
            Collections.newSetFromMap(new ConcurrentHashMap<>(64));

    /**
     * Cron解析器
     */
    private StringValueResolver resolver;

    /**
     * 调度器
     */
    private ThreadPoolTaskScheduler scheduler;

    @Override
    public Object postProcessAfterInitialization(@NonNull Object bean, String beanName) throws BeansException {
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
//        if (bean instanceof Runnable) {
//            if (targetClass.isAnnotationPresent(Scheduled.class)) {
//                Scheduled scheduled = targetClass.getDeclaredAnnotation(Scheduled.class);
//                processScheduled(scheduled, (Runnable) bean);
//            }
//        } else if (!this.nonAnnotatedClasses.contains(targetClass) &&
//                AnnotationUtils.isCandidateClass(targetClass, Scheduled.class)) {
//            Map<Method, Scheduled> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
//                    (MethodIntrospector.MetadataLookup<Scheduled>) method -> {
//                        return AnnotatedElementUtils.findMergedAnnotation(method, Scheduled.class);
////                        Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils
////                                .findAllMergedAnnotations(method, Scheduled.class);
////                        return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
//                    });
//            if (annotatedMethods.isEmpty()) {
//                this.nonAnnotatedClasses.add(targetClass);
//                logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
//            } else {
//                annotatedMethods.forEach((method, scheduled) -> {
//                            processScheduled(scheduled, bean, method);
//                        }
//                );
//                logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '"
//                        + beanName + "': " + annotatedMethods);
//            }
//        }

        return bean;
    }

    @Override
    public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
        scheduler = (ThreadPoolTaskScheduler) beanFactory.getBean("threadPoolTaskScheduler");
    }

    @Override
    public void setEmbeddedValueResolver(@NonNull StringValueResolver resolver) {
        this.resolver = resolver;
    }

    @Override
    public void afterSingletonsInstantiated() {
        this.nonAnnotatedClasses.clear();
    }

    /**
     * 执行计划任务
     */
    private void processScheduled(Scheduled scheduled, Runnable runnable) {
        Class<? extends Runnable> clazz = runnable.getClass();
        Trigger trigger = null;
        if (AnnotatedElementUtils.isAnnotated(clazz, CronScheduled.class)) {
            trigger = createCronTrigger(scheduled);
        } else if (AnnotatedElementUtils.isAnnotated(clazz, PeriodScheduled.class)) {
            PeriodScheduled annotation = clazz.getAnnotation(PeriodScheduled.class);
            trigger = createPeriodTrigger(annotation);
        }
        if (null != trigger) {
            scheduler.schedule(runnable, trigger);
        }
    }

    /**
     * 执行计划任务
     */
    private void processScheduled(Scheduled scheduled, Object bean, Method method) {
        Trigger trigger = null;
        if (AnnotatedElementUtils.isAnnotated(method, CronScheduled.class)) {
            trigger = createCronTrigger(scheduled);
        } else if (AnnotatedElementUtils.isAnnotated(method, PeriodScheduled.class)) {
            PeriodScheduled annotation = method.getAnnotation(PeriodScheduled.class);
            trigger = createPeriodTrigger(annotation);
        }
        if (null != trigger) {
            Runnable runnable = createRunnable(bean, method);
            TriggerTask task = new TriggerTask(runnable, trigger);
            scheduler.schedule(task.getRunnable(), task.getTrigger());
        }
    }

    private Trigger createPeriodTrigger(PeriodScheduled scheduled) {
        PeriodicTrigger trigger = new PeriodicTrigger(scheduled.period(), scheduled.timeUnit());
        trigger.setFixedRate(scheduled.fixedRate());
        trigger.setInitialDelay(scheduled.initialDelay());
        return trigger;
    }


    /**
     * 构建CronTask
     */
    private Trigger createCronTrigger(Annotation scheduled) {
        CronScheduled task = (CronScheduled) scheduled;
        String cron = task.cron();
        if (StringUtils.hasText(cron)) {
            String zone = task.zone();
            if (this.resolver != null) {
                cron = this.resolver.resolveStringValue(cron);
                zone = this.resolver.resolveStringValue(zone);
            }
            if (StringUtils.hasLength(cron)) {
                if (!CRON_DISABLED.equals(cron)) {
                    ZoneId zoneId = StringUtils.hasText(zone)
                            ? ZoneId.of(zone)
                            : ZoneId.systemDefault();
                   // return new CronTrigger(cron, zoneId);
                }
            }
        }
        return null;
    }

    /**
     * 构建runnable
     */
    private Runnable createRunnable(Object target, Method method) {
        Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated range @Scheduled");
        Method invokeMethod = AopUtils.selectInvocableMethod(method, target.getClass());
        return new ScheduledMethodRunnable(target, invokeMethod);
    }

    /**
     * 校验cron表达式
     */
    private static boolean isValidExpression(@Nullable String expression) {
        if (expression == null) {
            return false;
        }
        String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
        if (!(fields.length == 6)) {
            return false;
        }
        try {
            // CronExpression.parse(expression);
            return true;
        } catch (IllegalArgumentException ex) {
            return false;
        }
    }

    /**
     * BeanPostProcessor注册需要注意时机，防止将@Configuration容器中过早的初始化，造成一些Bug。 根据源码来讲，一般推荐两种方式：
     * 1.将BeanPostProcessor单独的放在一个@Configuration容器中； 2.若与其他Bean共用一个@Configuration容器，推荐使用static方法注册后置处理器；
     */
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
